package com.example.httpdigest.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;

import java.util.Base64;

/**
 * http 摘要认证流程
 *  1. 浏览器发出请求，说要访问 /hello 接口。
 *  2. 服务端返回 401，表示未认证，同时在响应头中携带 WWW-Authenticate 字段来描述认证形式。不同的是，这次服务端会计算出一个随机字符串，一同返回前端，这样可以防止重放攻击（所谓重放攻击就是别人嗅探到你的摘要信息，把摘要当成密码一次次发送服务端，加一个会变化的随机字符串，生成的摘要信息就会变化，就可以防止重放攻击），如下：Digest realm="myRealm", qop="auth", nonce="MTczMTkxNTQ1Nzk2MTo0ODg1YTdlODNhMGI1ZDMwZmFjOTY3ZTA4NjI3YzE1YQ=="
 *  3. 客户端选择一个算法，根据该算法计算出密码以及其他数据的摘要，如下：Digest username="wangnian", realm="myRealm", nonce="MTczMTkxNTk2ODY2NzpkZDA2ZmMwMDEwOTc5MmQxY2Y5MDY1ZDk3MjBjM2EwYg==", uri="/hello", response="7fe65a7db69b417f1931917d94dbef0b", qop=auth, nc=00000002, cnonce="e13db1b2bd6f1164"
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 配置无非就是两方面，一方面是服务端随机字符串的生成，另一方面就是客户端摘要信息的校验。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(digestAuthenticationEntryPoint())
                .and()
                .addFilter(digestAuthenticationFilter())
                .csrf().disable();
    }

    /**
     * 配置认证未通过的处理逻辑
     * 服务端返回 401，表示未认证，同时在响应头中携带 WWW-Authenticate 字段来描述认证形式。
     * www-authenticate:
     * Digest realm="myRealm", qop="auth", nonce="MTczMTkxNTQ1Nzk2MTo0ODg1YTdlODNhMGI1ZDMwZmFjOTY3ZTA4NjI3YzE1YQ=="
     */
    @Bean
    DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
        DigestAuthenticationEntryPoint entryPoint = new DigestAuthenticationEntryPoint();
        entryPoint.setKey("wangnian");
        entryPoint.setRealmName("myRealm");
        entryPoint.setNonceValiditySeconds(6);//摘要过期时间 60 秒
        return entryPoint;
    }

    @Bean
    DigestAuthenticationFilter digestAuthenticationFilter(){
        DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
        filter.setUserDetailsService(userDetailsService());
        filter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint());
        return filter;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("wangnian").password("123").roles("admin").build());
        return inMemoryUserDetailsManager;
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    public static void main(String[] args) {
        String s = "MTczMTkxNTQ1Nzk2MTo0ODg1YTdlODNhMGI1ZDMwZmFjOTY3ZTA4NjI3YzE1YQ";
        System.out.println(new String(Base64.getDecoder().decode(s)));
    }
}
